home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Applications / Newswatcher 2.0b22 / NW Source / Source / full.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-12-04  |  24.1 KB  |  900 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     full.c
  4.  
  5.     This module handles tasks involving the full group list.
  6.     
  7.     Copyright © 1994, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13.  
  14. #include "glob.h"
  15. #include "full.h"
  16. #include "qsort.h"
  17. #include "dialog.h"
  18. #include "news.h"
  19. #include "newswatcher.h"
  20. #include "menus.h"
  21. #include "status.h"
  22. #include "wind.h"
  23. #include "group.h"
  24. #include "text.h"
  25. #include "memutil.h"
  26. #include "strutil.h"
  27. #include "windutil.h"
  28. #include "subscribe.h"
  29. #include "listutil.h"
  30. #include "fileutil.h"
  31.  
  32.  
  33.  
  34. #define kMustCloseBeforeRebuildDlg        150
  35.  
  36.  
  37.  
  38. static Handle gSortGroupNames;
  39.  
  40.  
  41.  
  42. /*----------------------------------------------------------------------------
  43.     FindGroupIndex 
  44.     
  45.     Find the index of a group in the full group array gFullGroupArray.
  46.  
  47.     Entry:    name = group name.
  48.             gFullGroupArray sorted in increasing order by group name.
  49.  
  50.     Exit:    function result = index in gFullGroupArray of group, or -1 if not found.
  51. ----------------------------------------------------------------------------*/
  52.  
  53. short FindGroupIndex (char *name)
  54. {
  55.     long low = 0;
  56.     long high = gNumGroups-1;
  57.     long mid;
  58.     short compare;
  59.     
  60.     while (low <= high) {
  61.         mid = (low + high) >> 1;
  62.         compare = strcmp(name, *gGroupNames + (*gFullGroupArray)[mid].nameOffset);
  63.         if (compare == 0) {
  64.             return mid;
  65.         } else if (compare < 0) {
  66.             high = mid-1;
  67.         } else {
  68.             low = mid+1;
  69.         }
  70.     }
  71.     return -1;        
  72. }
  73.  
  74.  
  75.  
  76. /*----------------------------------------------------------------------------
  77.     GroupCompare 
  78.     
  79.     The group comparison routine used in the calls to FastQSort in 
  80.     SortFullGroupArray and CheckForNewGroups below. It does a 
  81.     simple string compare and gives time to background applications.
  82.     
  83.     Entry:    p = pointer to first group info struct.
  84.             q = pointer to second group info struct.
  85.             gSortGroupNames = handle to group names block.
  86.             
  87.     Exit:    function result
  88.                 < 0 if first group name < second group name.
  89.                 = 0 if first group name = second group name.
  90.                 > 0 if first group name > second group name.
  91. ----------------------------------------------------------------------------*/
  92.  
  93. static short GroupCompare (TGroup *p, TGroup *q)
  94. {
  95.     static short counter = 0;
  96.  
  97.     if((++counter & 0x1f) == 0) {
  98.         if (GiveTime(false) == userCanceledErr) CancelFastQSort();
  99.         counter = 0;
  100.     }
  101.  
  102.     return strcmp(*gSortGroupNames + p->nameOffset, *gSortGroupNames + q->nameOffset);
  103. }
  104.  
  105.  
  106.  
  107. /*----------------------------------------------------------------------------
  108.     SortFullGroupArray 
  109.     
  110.     Sort a full group array alphabetically by group name.
  111.     
  112.     Exit:    function result = error code.
  113. ----------------------------------------------------------------------------*/
  114.  
  115. static OSErr SortFullGroupArray (TGroup **groupArray, short numGroups, Handle groupNames)
  116. {
  117.     OSErr err = noErr;
  118.     char state;
  119.  
  120.     if (numGroups > 0) {
  121.         err = DisplayStatusMessageNumber(kStrSortingStatusMsg);
  122.         if (err != noErr) return err;
  123.         gSortGroupNames = groupNames;
  124.         state = MyHGetState(groupArray);
  125.         MyHLock(groupArray);
  126.         err = FastQSort(*groupArray, numGroups, sizeof(TGroup), 
  127.             (short(*)(void*,void*))GroupCompare);
  128.         MyHSetState(groupArray, state);
  129.         if (err != noErr) return err;
  130.     }
  131.     return noErr;
  132. }
  133.  
  134.  
  135.  
  136. /*----------------------------------------------------------------------------
  137.     InitializeGroupRecord 
  138.     
  139.     Initialize a new group record.
  140.     
  141.     Entry:    x = pointer to group record.
  142.             offset = offset in gGroupNames of group name.
  143. ----------------------------------------------------------------------------*/
  144.  
  145. static void InitializeGroupRecord (TGroup *x, long offset)
  146. {
  147.     x->nameOffset = offset;
  148.     x->firstMess = x->lastMess = 0;
  149.     x->numUnread = 0;
  150.     x->status = ' ';
  151.     x->unread = nil;
  152.     x->onlyRedrawCount = false;
  153. }
  154.  
  155.  
  156.  
  157. /*----------------------------------------------------------------------------
  158.     ReadGroupsFromPrefs 
  159.     
  160.     Read the full group list stored on the preferences file.
  161.     
  162.     Entry:    fSpec = pointer to file spec of prefs file.
  163.             prefsInDataFork = true if prefs are in data fork of prefs file
  164.                 and the full group list follows the prefs. False if the
  165.                 prefs are in the resource fork and the data fork contains
  166.                 only the full group list.
  167.             prefsVersion = version number of prefs file.
  168.  
  169.     Exit:    function result = error code.
  170. ----------------------------------------------------------------------------*/
  171.  
  172. OSErr ReadGroupsFromPrefs (FSSpec *fSpec, Boolean prefsInDataFork, 
  173.     unsigned long prefsVersion)
  174. {
  175.     OSErr err = noErr;
  176.     short fRefNum = 0, i;
  177.     Boolean needSort = false;
  178.     char *p, *pEnd;
  179.     char *prevName = nil;
  180.     char *curName;
  181.     TGroup *x;
  182.     long groupNamesSize;
  183.     char state;
  184.  
  185.     err = DisplayStatusMessageNumber(kStrReadingStatusMsg);
  186.     if (err != noErr) goto exit;
  187.         
  188.     err = FSpOpenDF(fSpec, fsRdPerm, &fRefNum);
  189.     if (err != noErr) return noErr;
  190.  
  191.     /* Read the saved group names. */
  192.     
  193.     err = GetEOF(fRefNum, &groupNamesSize);
  194.     if (err != noErr) goto exit;
  195.     if (prefsInDataFork) {
  196.         groupNamesSize -= sizeof(TPrefRec);
  197.         if (groupNamesSize < 0) goto exit;
  198.         err = SetFPos(fRefNum, fsFromStart, sizeof(TPrefRec));
  199.         if (err != noErr) goto exit;
  200.     }
  201.     err = MyNewHandle(groupNamesSize, &gGroupNames);
  202.     if (err != noErr) goto exit;
  203.     state = MyHGetState(gGroupNames);
  204.     MyHLock(gGroupNames);
  205.     err = FSRead(fRefNum, &groupNamesSize, *gGroupNames);
  206.     MyHSetState(gGroupNames, state);
  207.     if (err != noErr) goto exit;
  208.     MyFSClose(fRefNum, nil);
  209.     fRefNum = 0;
  210.     
  211.     /* Special case the Cornell version 2.0d15-CU, which put 
  212.        a 32 byte authorization username and 32 bytes of zero at 
  213.        the end of the NU 2.0d14 prefs, which in any NU version 
  214.        of NW shows up as the first 64 bytes of what we think 
  215.        is the full group list! (Yes, yuck). 
  216.        
  217.        First we check prefsVersion to see if it is 2.0d14. If 
  218.        it is, we then check byte 32 of the full group list. If 
  219.        byte 32 is 0, we assume this is a Cornell prefs file. In 
  220.        this case, we copy the first 32 bytes (the Cornell authorization 
  221.        username) to gPrefs.authUsername (our authorization username). 
  222.        We discard the next unused 32 zero bytes. 
  223.        
  224.        Note that in NU prefs files, a 0 byte never appears in the
  225.        full group list, so theoretically there is no chance of
  226.        confusing an NU prefs file with a CU prefs file.
  227.        
  228.        This makes NU NewsWatcher 2.0d27 and later understand and 
  229.        properly convert 2.0d15-CU prefs files. The reverse will never 
  230.        work - 2.0d15-CU is not able to make any sense of any NU version 
  231.        prefs file.
  232.     */
  233.  
  234.     if (prefsVersion == 0x02002014 && groupNamesSize >= 64 &&
  235.         *(*gGroupNames+32) == 0) 
  236.     {
  237.         BlockMoveData(*gGroupNames, gPrefs.authUsername, 32);
  238.         groupNamesSize -= 64;
  239.         BlockMoveData(*gGroupNames + 64, *gGroupNames, groupNamesSize);
  240.         MySetHandleSize(gGroupNames, groupNamesSize);
  241.         gFullGroupListDirty = true;
  242.     }    
  243.     
  244.     /* Check to make certain the last group name ends in CR. If not, strip
  245.        any trailing junk. */
  246.        
  247.     p = *gGroupNames + groupNamesSize - 1;
  248.     while (p >= *gGroupNames && *p != CR) p--;
  249.     p++;
  250.     if (p < *gGroupNames + groupNamesSize) {
  251.         groupNamesSize = p - *gGroupNames;
  252.         MySetHandleSize(gGroupNames, groupNamesSize);
  253.         gFullGroupListDirty = true;
  254.         if (p == *gGroupNames) return noErr;
  255.     }
  256.     
  257.     /* Walk through the gGroupNames buffer. Count the number of groups. 
  258.        Change all CR to 0. Check to see if the groups are already sorted
  259.        (they should be). */
  260.        
  261.     p = *gGroupNames;
  262.     pEnd = p + groupNamesSize;
  263.     gNumGroups = 0;
  264.     while (p < pEnd && gNumGroups < 16000) {
  265.         curName = p;
  266.         while (*p != CR) p++;
  267.         *p++ = 0;
  268.         gNumGroups++;
  269.         needSort = needSort || (prevName != nil && strcmp(prevName, curName) > 0);
  270.         prevName = curName;
  271.     }
  272.     
  273.     if (p < pEnd) {
  274.         groupNamesSize = p - *gGroupNames;
  275.         MySetHandleSize(gGroupNames, groupNamesSize);
  276.         gFullGroupListDirty = true;
  277.         ErrorMessageNumber(kStrTooManyPrefsFileGroups);
  278.     }
  279.     
  280.     /* Allocate and initialize the full group array. */
  281.     
  282.     err = MyNewHandle(gNumGroups*sizeof(TGroup), &gFullGroupArray);
  283.     if (err != noErr) goto exit;
  284.     
  285.     for (i = 0, x = *gFullGroupArray, p = *gGroupNames; i < gNumGroups; i++, x++) {
  286.         InitializeGroupRecord(x, p - *gGroupNames);
  287.         p += strlen(p) + 1;
  288.     }
  289.     
  290.     /* If necessary, sort the full group array. */
  291.  
  292.     if (needSort) {
  293.         gFullGroupListDirty = true;
  294.         err = SortFullGroupArray(gFullGroupArray, gNumGroups, gGroupNames);
  295.         if (err != noErr) goto exit;
  296.     }
  297.     
  298.     return noErr;
  299.     
  300. exit:
  301.  
  302.     MyDisposeHandle(gGroupNames);
  303.     if (fRefNum != 0) MyFSClose(fRefNum, nil);
  304.     gNumGroups = 0;
  305.     return err;
  306. }
  307.  
  308.  
  309.  
  310. /*----------------------------------------------------------------------------
  311.     AdjustFullGroupListChildWindows
  312.  
  313.     This function must be called whenever the full group list changes. It locates
  314.     all the open child subject list windows. If a group has been deleted, any
  315.     associated open child subject list window is closed. If a group still exists,
  316.     the "parentGroup" backpointer in the child window's TWindow info is adjusted
  317.     to point to the new location of the parent group in the full group array
  318.     gFullGroupArray.
  319.     
  320.     Exit:    function result = error code.
  321. ----------------------------------------------------------------------------*/
  322.  
  323. static OSErr AdjustFullGroupListChildWindows (void)
  324. {
  325.     TWindow **info, **childInfo;
  326.     TChild **childList, **prevChildList;
  327.     WindowPtr childWindow;
  328.     CStr255 groupName;
  329.     short index;
  330.     OSErr err = noErr;
  331.     
  332.     gFullGroupListDirty = true;
  333.     if (gFullGroupWindow == nil) return noErr;
  334.     info = (TWindow**)GetWRefCon(gFullGroupWindow);
  335.     childList = (**info).childList;
  336.     prevChildList = nil;
  337.     while (childList != nil) {
  338.         childWindow = (**childList).childWindow;
  339.         childInfo = (TWindow**)GetWRefCon(childWindow);
  340.         strcpy(groupName, *gGroupNames + (**childInfo).groupNameOffset);
  341.         index = FindGroupIndex(groupName);
  342.         if (index == -1) {
  343.             /* Group has been deleted. Close the child window. */
  344.             err = DoClose(childWindow);
  345.             if (err != noErr) return err;
  346.             if (prevChildList == nil) {
  347.                 childList = (**info).childList;
  348.             } else {
  349.                 childList = (**prevChildList).next;
  350.             }
  351.         } else {
  352.             /* Group still exists. Update the parentGroup backpointer. */
  353.             (**childInfo).parentGroup = index;
  354.             prevChildList = childList;
  355.             childList = (**childList).next;
  356.         }
  357.     }
  358.     return noErr;
  359. }
  360.  
  361.  
  362.  
  363. /*----------------------------------------------------------------------------
  364.     UpdateFullGroupWindow 
  365.     
  366.     Make sure that the Full Group List window corresponds to the changed 
  367.     full group list. It must be called whenever the full group list changes.
  368.     
  369.     Exit:    function result = error code.
  370. ----------------------------------------------------------------------------*/
  371.  
  372. static OSErr UpdateFullGroupWindow (void)
  373. {
  374.     TWindow **info;
  375.     Point thePt;
  376.     GrafPtr port;
  377.     OSErr err = noErr;
  378.  
  379.     GetPort(&port);
  380.     
  381.     err = AdjustFullGroupListChildWindows();
  382.     if (err != noErr) return err;
  383.     if (gFullGroupWindow != nil) {
  384.         info = (TWindow**)GetWRefCon(gFullGroupWindow);
  385.         (**info).groupArray = gFullGroupArray;
  386.         (**info).numGroups = gNumGroups;
  387.         err = MakeGroupList(gNumGroups, (**info).theList);
  388.         if (err != noErr) return err;
  389.         SetPt(&thePt, 0, 0);
  390.         MyLSetSelect(true, thePt, (**info).theList);
  391.         SetPort(gFullGroupWindow);
  392.         InvalRect(&gFullGroupWindow->portRect);
  393.     }
  394.     
  395.     SetPort(port);
  396.     return noErr;
  397. }
  398.  
  399.  
  400.  
  401. /*----------------------------------------------------------------------------
  402.     MergeNewGroupsIntoFullGroupList 
  403.     
  404.     Merge new groups into the full group list. Both lists must be sorted on 
  405.     entry. The full group list remains sorted on exit.
  406.     
  407.     Entry:    newGroupsArray = handle to new groups array.
  408.             numNew = number of new groups.
  409.     
  410.     Exit:    function result = error code.
  411. ----------------------------------------------------------------------------*/
  412.  
  413. static OSErr MergeNewGroupsIntoFullGroupList (TGroup **newGroupsArray, short numNew)
  414. {
  415.     short numLeftToInsert, numToMoveUp;
  416.     TGroup *fullListPtr, *newListPtr;
  417.     char *newName;
  418.     OSErr err = noErr;
  419.  
  420.     gNumGroups += numNew;
  421.     err = MySetHandleSize(gFullGroupArray, gNumGroups*sizeof(TGroup));
  422.     if (err != noErr) return err;
  423.     
  424.     numLeftToInsert = numNew;
  425.     fullListPtr = *gFullGroupArray + gNumGroups - numNew - 1;
  426.     newListPtr = *newGroupsArray + numNew - 1;
  427.     while (numLeftToInsert > 0) {
  428.         newName = *gGroupNames + newListPtr->nameOffset;
  429.         numToMoveUp = 0;
  430.         while (fullListPtr >= *gFullGroupArray &&
  431.             strcmp(newName, *gGroupNames + fullListPtr->nameOffset) <= 0) 
  432.         {
  433.             fullListPtr--;
  434.             numToMoveUp++;
  435.         }
  436.         if (numToMoveUp > 0)
  437.             BlockMoveData(fullListPtr + 1, fullListPtr + numLeftToInsert + 1, 
  438.                 numToMoveUp*sizeof(TGroup));
  439.         BlockMoveData(newListPtr, fullListPtr + numLeftToInsert, sizeof(TGroup));
  440.         newListPtr--;
  441.         numLeftToInsert--;
  442.     }
  443.     
  444.     return noErr;
  445. }
  446.  
  447.  
  448.  
  449. /*----------------------------------------------------------------------------
  450.     CheckForNewGroups 
  451.     
  452.     Check for any new groups created since last time we checked.
  453.  
  454.     Exit:    function result = error code.
  455.             *newGroupsArray = handle to group array, or nil if numNewGroups == 0.
  456.             *numNewGroups = number of groups in group array.
  457. ----------------------------------------------------------------------------*/
  458.  
  459. OSErr CheckForNewGroups (TGroup ***newGroupsArray, short *numNewGroups)
  460. {
  461.     OSErr err = noErr;
  462.     short len, nameWidth;
  463.     long numGroups, numNew, i;
  464.     Handle strings = nil;
  465.     char *p, *q;
  466.     long offset, savedGroupNamesSize;
  467.     TGroup **groupArray = nil;
  468.     TGroup *x;
  469.     CStr255 groupName;
  470.     GrafPtr port;
  471.     char state;
  472.     WindowPtr wind;
  473.     TWindow **info;
  474.     Handle unsubscribed;
  475.     
  476.     GetPort(&port);
  477.     savedGroupNamesSize = GetHandleSize(gGroupNames);
  478.     
  479.     err = DisplayStatusMessageNumber(kStrCheckingNewStatusMsg);
  480.     if (err != noErr) goto exit;
  481.         
  482.     /* Get the new group names from the server. */
  483.     
  484.     err = GetGroupNames(gPrefs.groupCheckTime, &strings, &numGroups);
  485.     if (err != noErr) goto exit;
  486.     
  487.     /* Check for duplicates. Filter out the groups which are already present in
  488.        the full group list. Check for too many new groups. */
  489.  
  490.     numNew = 0;
  491.     for (i = 0, p = *strings, q = *strings; i < numGroups; i++) {
  492.         len = strlen(q);
  493.         if (FindGroupIndex(q) == -1) {
  494.             if (gNumGroups + numNew >= 16000) break;
  495.             strcpy(p, q);
  496.             p += len+1;
  497.             numNew++;
  498.         }
  499.         q += len+1;
  500.     }
  501.     
  502.     MySetHandleSize(strings, p - *strings);
  503.     
  504.     if (i < numGroups) ErrorMessageNumber(kStrTooManyGroupsOnServer);
  505.     
  506.     if (numNew == 0) {
  507.         MyDisposeHandle(strings);
  508.         *numNewGroups = 0;
  509.         *newGroupsArray = nil;
  510.         SetPort(port);
  511.         return noErr;
  512.     }
  513.     
  514.     /* Append the new group names to the end of gGroupNames. */
  515.     
  516.     offset = savedGroupNamesSize;
  517.     err = MyHandAndHand(strings, gGroupNames);
  518.     if (err != noErr) goto exit;
  519.     
  520.     /* Allocate and initialize the group array for the new groups. */
  521.     
  522.     err = MyNewHandle(numNew * sizeof(TGroup), &groupArray);
  523.     if (err != noErr) goto exit;
  524.     
  525.     for (i = 0, x = *groupArray, p = *gGroupNames + offset; i < numNew; i++, x++) {
  526.         InitializeGroupRecord(x, p - *gGroupNames);
  527.         p += strlen(p) + 1;
  528.     }
  529.     
  530.     /* Sort the new groups array. */
  531.     
  532.     gSortGroupNames = gGroupNames;
  533.     state = MyHGetState(groupArray);
  534.     MyHLock(groupArray);
  535.     err = FastQSort(*groupArray, numNew, sizeof(TGroup), 
  536.         (short(*)(void*,void*))GroupCompare);
  537.     MyHSetState(groupArray, state);
  538.     if (err != noErr) goto exit;
  539.     
  540.     /* Merge the new groups into the full group list. */
  541.  
  542.     err = MergeNewGroupsIntoFullGroupList(groupArray, numNew);
  543.     if (err != noErr) goto exit;
  544.     
  545.     err = UpdateFullGroupWindow();
  546.     if (err != noErr) goto exit;
  547.     
  548.     /* Update the last new groups check date and time. */
  549.     
  550.     GetDateTime(&gPrefs.groupCheckTime);
  551.     
  552.     /* Check to see if one of the new group names is now the widest group 
  553.        name in the full group list window. */
  554.     
  555.     SetPort(gFullGroupWindow);
  556.     if (gPrefs.maxGroupNameWidth > 0) {
  557.         for (i = 0; i < numNew; i++) {
  558.             x = &(*groupArray)[i];
  559.             strcpy(groupName, *gGroupNames + x->nameOffset); 
  560.             nameWidth = TextWidth(groupName, 0, strlen(groupName));
  561.             if (nameWidth > gPrefs.maxGroupNameWidth) gPrefs.maxGroupNameWidth = nameWidth;
  562.         }
  563.     }
  564.     
  565.     /* Add each new group name to the unsubscribed list of each open user group
  566.        list window. */
  567.        
  568.     wind = FrontWindow();
  569.     while (wind != nil) {
  570.         if (GetWindowKind(wind) == kGroup) {
  571.             info = (TWindow**)GetWRefCon(wind);
  572.             unsubscribed = (**info).unsubscribed;
  573.             if (unsubscribed != nil) {
  574.                 for (i = 0; i < numNew; i++) {
  575.                     x = &(*groupArray)[i];
  576.                     strcpy(groupName, *gGroupNames + x->nameOffset);
  577.                     err = AddGroupToUnsubscribedList(groupName, unsubscribed);
  578.                     if (err != noErr) goto exit;
  579.                 }
  580.             }
  581.         }
  582.         wind = (WindowPtr)((WindowPeek)wind)->nextWindow;
  583.     }
  584.     
  585.     /* Return. */
  586.     
  587.     *newGroupsArray = groupArray;
  588.     *numNewGroups = numNew;
  589.     SetPort(port);
  590.     return noErr;
  591.     
  592. exit:
  593.  
  594.     MyDisposeHandle(strings);
  595.     MyDisposeHandle(groupArray);
  596.     MySetHandleSize(gGroupNames, savedGroupNamesSize);
  597.     SetPort(port);
  598.     return err;
  599. }
  600.  
  601.  
  602.  
  603. /*----------------------------------------------------------------------------
  604.     DoCheckForNewGroups
  605.  
  606.     Handle the "Check for New Groups" command.
  607.     
  608.     Exit:    function result = error code.
  609. ----------------------------------------------------------------------------*/
  610.  
  611. OSErr DoCheckForNewGroups (void)
  612. {
  613.     TGroup **groupArray;
  614.     short numGroups;
  615.     WindowPtr wind;
  616.     OSErr err = noErr;
  617.  
  618.     err = CheckForNewGroups(&groupArray, &numGroups);
  619.     if (err != noErr) return err;
  620.     if (numGroups > 0) {
  621.         return MakeNewGroupsWindow(groupArray, numGroups, &wind);
  622.     } else {
  623.         return noErr;
  624.     }
  625. }
  626.  
  627.  
  628.  
  629. /*----------------------------------------------------------------------------
  630.     DoCheckForDeletedGroups 
  631.     
  632.     Handle the "Check for Deleted Groups" command.
  633.     
  634.     Exit:    function result = error code.
  635. ----------------------------------------------------------------------------*/
  636.  
  637. OSErr DoCheckForDeletedGroups (void)
  638. {
  639.     Handle strings = nil;
  640.     Handle deleted = nil;
  641.     long deletedNext, deletedAllocated, offset;
  642.     short index, len, nameWidth, numDel, numToMoveDown;
  643.     long numGroups, i;
  644.     TGroup *x, *prev, *cur, *curEnd;
  645.     char *p;
  646.     OSErr err = noErr;
  647.     CStr255 groupName;
  648.     GrafPtr port;
  649.     Str255 title;
  650.     WindowPtr wind;
  651.     
  652.     GetPort(&port);
  653.  
  654.     err = DisplayStatusMessageNumber(kStrCheckingDelStatusMsg);
  655.     if (err != noErr) goto exit;
  656.     
  657.     /* Get a list of all group names from the server. */
  658.     
  659.     err = GetGroupNames(0, &strings, &numGroups);
  660.     if (err != noErr) goto exit;
  661.     
  662.     /* Mark all the groups in the full group list with status = 'd'. Then walk
  663.        the fresh full group list we just got from the server and mark all
  664.        the groups which still exist with status = ' '. This leaves just the
  665.        deleted groups marked with status = 'd'. */
  666.     
  667.     for (i = 0, x = *gFullGroupArray; i < gNumGroups; i++, x++) x->status = 'd';
  668.     
  669.     for (i = 0, offset = 0; i < numGroups; i++) {
  670.         err = GiveTime(false);
  671.         if (err != noErr) goto exit;
  672.         p = *strings + offset;
  673.         index = FindGroupIndex(p);
  674.         if (index != -1) (*gFullGroupArray)[index].status = ' ';
  675.         offset += strlen(p) + 1;
  676.     }
  677.     MyDisposeHandle(strings);
  678.     strings = nil;
  679.     
  680.     /* Allocate a buffer to hold the names of the deleted groups for display
  681.        to the user. */
  682.     
  683.     err = MyNewHandle(0, &deleted);
  684.     if (err != noErr) goto exit;
  685.     deletedNext = deletedAllocated = 0;
  686.     
  687.     /* Walk the full group list. Copy the names of the deleted groups to the 
  688.        buffer. Also check to see if the group with the widest name in the full
  689.        group list window has been deleted. */
  690.     
  691.     SetPort(gFullGroupWindow);
  692.     numDel = 0;
  693.     for (i = 0; i < gNumGroups; i++) {
  694.         x = &(*gFullGroupArray)[i];
  695.         if (x->status == 'd') {
  696.             err = GiveTime(false);
  697.             if (err != noErr) goto exit;
  698.             numDel++;
  699.             strcpy(groupName, *gGroupNames + x->nameOffset);
  700.             len = strlen(groupName);
  701.             if (deletedNext + len + 1 > deletedAllocated) {
  702.                 deletedAllocated += 1000;
  703.                 err = MySetHandleSize(deleted, deletedAllocated);
  704.                 if (err != noErr) goto exit;
  705.             }
  706.             strcpy(*deleted + deletedNext, groupName);
  707.             deletedNext += len+1;
  708.             *(*deleted + deletedNext - 1) = CR;
  709.             if (gPrefs.maxGroupNameWidth > 0) {
  710.                 nameWidth = TextWidth(groupName, 0, len);
  711.                 if (nameWidth >= gPrefs.maxGroupNameWidth) gPrefs.maxGroupNameWidth = 0;
  712.             }
  713.         }
  714.     }
  715.     
  716.     /* If there aren't any deleted groups, issue a note message and return. */
  717.     
  718.     if (numDel == 0) {
  719.         MyDisposeHandle(deleted);
  720.         NoteMessageNumber(kStrNoDelGroups);
  721.         return noErr;
  722.     }
  723.     
  724.     MySetHandleSize(deleted, deletedNext);
  725.     
  726.     /* Remove the deleted groups from the full group list. */
  727.     
  728.     prev = *gFullGroupArray;
  729.     cur = *gFullGroupArray;
  730.     curEnd = *gFullGroupArray + gNumGroups;
  731.     numDel = 0;
  732.     while (cur < curEnd) {
  733.         numToMoveDown = 0;
  734.         while (cur < curEnd && cur->status != 'd') {
  735.             cur++;
  736.             numToMoveDown++;
  737.         }
  738.         if (numDel > 0 && numToMoveDown > 0)
  739.             BlockMoveData(prev + numDel, prev, numToMoveDown*sizeof(TGroup));
  740.         prev += numToMoveDown;
  741.         numDel++;
  742.         cur++;
  743.     }
  744.     
  745.     /* Update the full group list window, display the deleted group names to the
  746.        user in a text window, and return. */
  747.     
  748.     gNumGroups -= numDel;
  749.     MySetHandleSize(gFullGroupArray, gNumGroups*sizeof(TGroup));
  750.     err = UpdateFullGroupWindow();
  751.     if (err != noErr) goto exit;
  752.     GetPString(kStrDelGroupsWindTitle, title);
  753.     err = MakeNewTextWindow(title, 0, nil, deleted, &wind);
  754.     if (err != noErr) goto exit;
  755.     
  756.     MyDisposeHandle(deleted);
  757.     SetPort(port);
  758.     return noErr;
  759.     
  760. exit:
  761.  
  762.     MyDisposeHandle(strings);
  763.     MyDisposeHandle(deleted);
  764.     SetPort(port);
  765.     return err;
  766. }
  767.  
  768.  
  769.  
  770. /*----------------------------------------------------------------------------
  771.     MustCloseBeforeRebuildDialog 
  772.     
  773.     Present the "must close windows before rebuilding full group list" dialog.
  774.             
  775.     Exit:    function result = error code.
  776. ----------------------------------------------------------------------------*/
  777.  
  778. static OSErr MustCloseBeforeRebuildDialog (void)
  779. {
  780.     OSErr err = noErr;
  781.     DialogPtr dlg = nil;
  782.     short item;
  783.     
  784.     err = MyGetNewDialog(kMustCloseBeforeRebuildDlg, ok, cancel, &dlg);
  785.     if (err != noErr) return err;
  786.     SysBeep(0);
  787.     MyModalDialog(dlg, gDialogFilterUPP, &item);
  788.     err = DoClose(dlg);
  789.     if (err != noErr) return err;
  790.     if (item == cancel) return userCanceledErr;
  791.     return noErr;
  792. }
  793.  
  794.  
  795.  
  796. /*----------------------------------------------------------------------------
  797.     DoRebuildFullGroupList 
  798.     
  799.     Handle the "Rebuild Full Group List" command.
  800.  
  801.     Exit:    function result = error code.
  802. ----------------------------------------------------------------------------*/
  803.  
  804. OSErr DoRebuildFullGroupList (void)
  805. {
  806.     TGroup **newFullGroupArray = nil;
  807.     long newNumGroups;
  808.     Handle newGroupNames = nil;
  809.     TGroup *x;
  810.     long i;
  811.     char *p;
  812.     OSErr err = noErr;
  813.     WindowPtr wind;
  814.     TWindowKind kind;
  815.     Boolean promptBeforeClose = true;
  816.     
  817.     /* Close all group windows except for the full group list window, and close
  818.        all subject windows. We must do this because group and subject window data 
  819.        structures contain offsets into the old group names gGroupNames, which is 
  820.        about to be blown away and replaced. */
  821.     
  822.     while (true) {
  823.         wind = FrontWindow();
  824.         while (wind != nil) {
  825.             kind = GetWindowKind(wind);
  826.             if ((kind == kGroup && wind != gFullGroupWindow) ||
  827.                 kind == kSubject) break;
  828.             wind = (WindowPtr)((WindowPeek)wind)->nextWindow;
  829.         }
  830.         if (wind == nil) break;
  831.         if (promptBeforeClose) {
  832.             err = MustCloseBeforeRebuildDialog();
  833.             if (err != noErr) goto exit;
  834.             promptBeforeClose = false;
  835.         }
  836.         err = DoClose(wind);
  837.         if (err != noErr) goto exit;
  838.     }
  839.     
  840.     /* Display the status message. */
  841.     
  842.     err = DisplayStatusMessageNumber(kStrGetFullStatusMsg);
  843.     if (err != noErr) goto exit;
  844.     
  845.     /* Get a list of all group names from the server. */
  846.     
  847.     err = GetGroupNames(0, &newGroupNames, &newNumGroups);
  848.     if (err != noErr) goto exit;
  849.     
  850.     /* Check for too many groups. */
  851.     
  852.     if (newNumGroups > 16000) {
  853.         newNumGroups = 16000;
  854.         ErrorMessageNumber(kStrTooManyGroupsOnServer);
  855.     }
  856.  
  857.     /* Allocate the new full group array. */
  858.  
  859.     err = MyNewHandle(newNumGroups * sizeof(TGroup), &newFullGroupArray);
  860.     if (err != noErr) goto exit;
  861.     
  862.     /* Initialize the new full group array. */
  863.     
  864.     for (i = 0, x = *newFullGroupArray, p = *newGroupNames; i < newNumGroups; i++, x++) {
  865.         InitializeGroupRecord(x, p - *newGroupNames);
  866.         p += strlen(p) + 1;
  867.     }
  868.     MySetHandleSize(newGroupNames, p - *newGroupNames);
  869.     
  870.     /* Sort the new full group array. */
  871.     
  872.     err = SortFullGroupArray(newFullGroupArray, newNumGroups, newGroupNames);
  873.     if (err != noErr) goto exit;
  874.     
  875.     /* Make the new full group array and group names the real ones. Dispose the old
  876.        ones. */
  877.        
  878.     MyDisposeHandle(gFullGroupArray);
  879.     gFullGroupArray = newFullGroupArray;
  880.     newFullGroupArray = nil;
  881.     gNumGroups = newNumGroups;
  882.     MyDisposeHandle(gGroupNames);
  883.     gGroupNames = newGroupNames;
  884.     newGroupNames = nil;
  885.     
  886.     /* Update the full group list window and return. */
  887.     
  888.     gPrefs.maxGroupNameWidth = 0;
  889.     err = UpdateFullGroupWindow();
  890.     if (err != noErr) goto exit;
  891.     GetDateTime(&gPrefs.groupCheckTime);
  892.     return noErr;
  893.     
  894. exit:
  895.  
  896.     MyDisposeHandle(newFullGroupArray);
  897.     MyDisposeHandle(newGroupNames);
  898.     return err;
  899. }
  900.